 /*******************************************************************************
  * Copyright (c) 2000, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  * Randy Hudson <hudsonr@us.ibm.com>
  * - Fix for bug 19524 - Resizing WorkbenchWindow resizes Views
  * Cagatay Kavukcuoglu <cagatayk@acm.org>
  * - Fix for bug 10025 - Resizing views should not use height ratios
  *******************************************************************************/
 package org.eclipse.ui.internal;

 import java.util.ArrayList ;

 import org.eclipse.core.runtime.Assert;
 import org.eclipse.jface.util.Geometry;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.graphics.Point;
 import org.eclipse.swt.graphics.Rectangle;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.ui.ISizeProvider;

 /**
  * Implementation of a tree where the node is allways a sash
  * and it allways has two chidren. If a children is removed
  * the sash, ie the node, is removed as well and its other children
  * placed on its parent.
  */
 public class LayoutTree implements ISizeProvider {
     /* The parent of this tree or null if it is the root */
     LayoutTreeNode parent;

     /* Any LayoutPart if this is a leaf or a LayoutSashPart if it is a node */
     LayoutPart part;
     
     // Cached information
 private int cachedMinimumWidthHint = SWT.DEFAULT;
     private int cachedMinimumWidth = SWT.DEFAULT;
     private int cachedMinimumHeightHint = SWT.DEFAULT;
     private int cachedMinimumHeight = SWT.DEFAULT;
     private int cachedMaximumWidthHint = SWT.DEFAULT;
     private int cachedMaximumWidth = SWT.DEFAULT;
     private int cachedMaximumHeightHint = SWT.DEFAULT;
     private int cachedMaximumHeight = SWT.DEFAULT;
     
     // Cached size flags
 private boolean sizeFlagsDirty = true;
     private int widthSizeFlags = 0;
     private int heightSizeFlags = 0;
     
     // Cache statistics. For use in benchmarks and test suites only!
 public static int minCacheHits;
     public static int minCacheMisses;
     public static int maxCacheHits;
     public static int maxCacheMisses;
     
     private boolean forceLayout = true;
     private Rectangle currentBounds = new Rectangle(0,0,0,0);
     
     /**
      * Initialize this tree with its part.
      */
     public LayoutTree(LayoutPart part) {
         this.part = part;
     }

     /**
      * Add the relation ship between the children in the list
      * and returns the left children.
      */
     public LayoutPart computeRelation(ArrayList relations) {
         return part;
     }

     /**
      * Locates the part that intersects the given point
      *
      * @param toFind
      * @return
      */
     public LayoutPart findPart(Point toFind) {
         return part;
     }
     
     /**
      * Dispose all Sashs in this tree
      */
     public void disposeSashes() {
     }
     
     /**
      * Find a LayoutPart in the tree and return its sub-tree. Returns
      * null if the child is not found.
      */
     public LayoutTree find(LayoutPart child) {
         if (part != child) {
             return null;
         }
         return this;
     }

     /**
      * Find the Left,Right,Top and Botton
      * sashes around this tree and set them
      * in <code>sashes</code>
      */
     public void findSashes(PartPane.Sashes sashes) {
         if (getParent() == null) {
             return;
         }
         getParent().findSashes(this, sashes);
     }

     /**
      * Find the part that is in the bottom rigth possition.
      */
     public LayoutPart findBottomRight() {
         return part;
     }
     
     /**
      * Find a sash in the tree and return its sub-tree. Returns
      * null if the sash is not found.
      */
     public LayoutTreeNode findSash(LayoutPartSash sash) {
         return null;
     }

     /**
      * Return the bounds of this tree which is the rectangle that
      * contains all Controls in this tree.
      */
     public final Rectangle getBounds() {
         return Geometry.copy(currentBounds);
     }
     
     /**
      * Subtracts two integers. If a is INFINITE, this is treated as
      * positive infinity.
      *
      * @param a a positive integer or INFINITE indicating positive infinity
      * @param b a positive integer (may not be INFINITE)
      * @return a - b, or INFINITE if a == INFINITE
      * @since 3.1
      */
     public static int subtract(int a, int b) {
         Assert.isTrue(b >= 0 && b < INFINITE);
         
         return add(a, -b);
     }
     
     /**
      * Adds two positive integers. Treates INFINITE as positive infinity.
      *
      * @param a a positive integer
      * @param b a positive integer
      * @return a + b, or INFINITE if a or b are positive infinity
      * @since 3.1
      */
     public static int add(int a, int b) {
         if (a == INFINITE || b == INFINITE) {
             return INFINITE;
         }
         
         return a + b;
     }
     
     /**
      * Asserts that toCheck is a positive integer less than INFINITE / 2 or equal
      * to INFINITE. Many of the methods of this class use positive integers as sizes,
      * with INFINITE indicating positive infinity. This picks up accidental addition or
      * subtraction from infinity.
      *
      * @param toCheck integer to validate
      * @since 3.1
      */
     public static void assertValidSize(int toCheck) {
         Assert.isTrue(toCheck >= 0 && (toCheck == INFINITE || toCheck < INFINITE / 2));
     }
     
     /**
      * Computes the preferred size for this object. The interpretation of the result depends on the flags returned
      * by getSizeFlags(). If the caller is looking for a maximum or minimum size, this delegates to computeMinimumSize
      * or computeMaximumSize in order to benefit from caching optimizations. Otherwise, it delegates to
      * doComputePreferredSize. Subclasses should overload one of doComputeMinimumSize, doComputeMaximumSize, or
      * doComputePreferredSize to specialize the return value.
      *
      * @see LayoutPart#computePreferredSize(boolean, int, int, int)
      */
     public final int computePreferredSize(boolean width, int availableParallel, int availablePerpendicular, int preferredParallel) {
         assertValidSize(availableParallel);
         assertValidSize(availablePerpendicular);
         assertValidSize(preferredParallel);
         
         if (!isVisible()) {
             return 0;
         }

         if (availableParallel == 0) {
             return 0;
         }

         if (preferredParallel == 0) {
             return Math.min(availableParallel, computeMinimumSize(width, availablePerpendicular));
         } else if (preferredParallel == INFINITE && availableParallel == INFINITE) {
             return computeMaximumSize(width, availablePerpendicular);
         }
         
         // Optimization: if this subtree doesn't have any size preferences beyond its minimum and maximum
 // size, simply return the preferred size
 if (!hasSizeFlag(width, SWT.FILL)) {
             return preferredParallel;
         }
         
         int result = doComputePreferredSize(width, availableParallel, availablePerpendicular, preferredParallel);

         return result;
     }
     
     /**
      * Returns the size flags for this tree.
      *
      * @see org.eclipse.ui.presentations.StackPresentation#getSizeFlags(boolean)
      *
      * @param b indicates whether the caller wants the flags for computing widths (=true) or heights (=false)
      * @return a bitwise combiniation of flags with the same meaning as StackPresentation.getSizeFlags(boolean)
      */
     protected int doGetSizeFlags(boolean width) {
         return part.getSizeFlags(width);
     }

     /**
      * Subclasses should overload this method instead of computePreferredSize(boolean, int, int, int)
      *
      * @see org.eclipse.ui.presentations.StackPresentation#computePreferredSize(boolean, int, int, int)
      *
      * @since 3.1
      */
     protected int doComputePreferredSize(boolean width, int availableParallel, int availablePerpendicular, int preferredParallel) {
         int result = Math.min(availableParallel,
                 part.computePreferredSize(width, availableParallel, availablePerpendicular, preferredParallel));

         assertValidSize(result);
         return result;
     }
     
     /**
      * Returns the minimum size for this subtree. Equivalent to calling
      * computePreferredSize(width, INFINITE, availablePerpendicular, 0).
      * Returns a cached value if possible or defers to doComputeMinimumSize otherwise.
      * Subclasses should overload doComputeMinimumSize if they want to specialize the
      * return value.
      *
      * @param width true iff computing the minimum width, false iff computing the minimum height
      * @param availablePerpendicular available space (pixels) perpendicular to the dimension
      * being computed. This is a height when computing a width, or a width when computing a height.
      *
      * @see LayoutPart#computePreferredSize(boolean, int, int, int)
      */
     public final int computeMinimumSize(boolean width, int availablePerpendicular) {
         assertValidSize(availablePerpendicular);
         
         // Optimization: if this subtree has no minimum size, then always return 0 as its
 // minimum size.
 if (!hasSizeFlag(width, SWT.MIN)) {
             return 0;
         }
         
         // If this subtree doesn't contain any wrapping controls (ie: they don't care
 // about their perpendicular size) then force the perpendicular
 // size to be INFINITE. This ensures that we will get a cache hit
 // every time for non-wrapping controls.
 if (!hasSizeFlag(width, SWT.WRAP)) {
             availablePerpendicular = INFINITE;
         }
         
         if (width) {
             // Check if we have a cached width measurement (we can only return a cached
 // value if we computed it for the same height)
 if (cachedMinimumWidthHint == availablePerpendicular) {
                 minCacheHits++;
                 return cachedMinimumWidth;
             }
             
             // Recompute the minimum width and store it in the cache

             minCacheMisses++;
              
             int result = doComputeMinimumSize(width, availablePerpendicular);
             cachedMinimumWidth = result;
             cachedMinimumWidthHint = availablePerpendicular;
             return result;
             
         } else {
             // Check if we have a cached height measurement (we can only return a cached
 // value if we computed it for the same width)
 if (cachedMinimumHeightHint == availablePerpendicular) {
                 minCacheHits++;
                 return cachedMinimumHeight;
             }
             
             // Recompute the minimum width and store it in the cache
 minCacheMisses++;
             
             int result = doComputeMinimumSize(width, availablePerpendicular);
             cachedMinimumHeight = result;
             cachedMinimumHeightHint = availablePerpendicular;
             return result;
         }
     }
     
     /**
      * For use in benchmarks and test suites only. Displays cache utilization statistics for all
      * LayoutTree instances.
      *
      * @since 3.1
      */
     public static void printCacheStatistics() {
         System.out.println("minimize cache " + minCacheHits + " / " + (minCacheHits + minCacheMisses) + " hits " + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 minCacheHits * 100 / (minCacheHits + minCacheMisses) + "%"); //$NON-NLS-1$
 System.out.println("maximize cache " + maxCacheHits + " / " + (maxCacheHits + maxCacheMisses) + " hits" + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 maxCacheHits * 100 / (maxCacheHits + maxCacheMisses) + "%"); //$NON-NLS-1$
 }
     
     public int doComputeMinimumSize(boolean width, int availablePerpendicular) {
         int result = doComputePreferredSize(width, INFINITE, availablePerpendicular, 0);
         assertValidSize(result);
         return result;
     }

     public final int computeMaximumSize(boolean width, int availablePerpendicular) {
         assertValidSize(availablePerpendicular);
         
         // Optimization: if this subtree has no maximum size, then always return INFINITE as its
 // maximum size.
 if (!hasSizeFlag(width, SWT.MAX)) {
             return INFINITE;
         }
         
         // If this subtree doesn't contain any wrapping controls (ie: they don't care
 // about their perpendicular size) then force the perpendicular
 // size to be INFINITE. This ensures that we will get a cache hit
 // every time.
 if (!hasSizeFlag(width, SWT.WRAP)) {
             availablePerpendicular = INFINITE;
         }
         
         if (width) {
             // Check if we have a cached width measurement (we can only return a cached
 // value if we computed it for the same height)
 if (cachedMaximumWidthHint == availablePerpendicular) {
                 maxCacheHits++;
                 return cachedMaximumWidth;
             }
             
             maxCacheMisses++;
             
             // Recompute the maximum width and store it in the cache
 int result = doComputeMaximumSize(width, availablePerpendicular);
             cachedMaximumWidth = result;
             cachedMaximumWidthHint = availablePerpendicular;
             return result;
             
         } else {
             // Check if we have a cached height measurement
 if (cachedMaximumHeightHint == availablePerpendicular) {
                 maxCacheHits++;
                 return cachedMaximumHeight;
             }
             
             maxCacheMisses++;
             
             // Recompute the maximum height and store it in the cache
 int result = doComputeMaximumSize(width, availablePerpendicular);
             cachedMaximumHeight = result;
             cachedMaximumHeightHint = availablePerpendicular;
             return result;
         }
     }
     
     protected int doComputeMaximumSize(boolean width, int availablePerpendicular) {
         return doComputePreferredSize(width, INFINITE, availablePerpendicular, INFINITE);
     }
     
     /**
      * Called to flush any cached information in this tree and its parents.
      */
     public void flushNode() {
         
         // Clear cached sizes
 cachedMinimumWidthHint = SWT.DEFAULT;
         cachedMinimumWidth = SWT.DEFAULT;
         cachedMinimumHeightHint = SWT.DEFAULT;
         cachedMinimumHeight = SWT.DEFAULT;
         cachedMaximumWidthHint = SWT.DEFAULT;
         cachedMaximumWidth = SWT.DEFAULT;
         cachedMaximumHeightHint = SWT.DEFAULT;
         cachedMaximumHeight = SWT.DEFAULT;
         
         // Flags may have changed. Ensure that they are recomputed the next time around
 sizeFlagsDirty = true;
         
         // The next setBounds call should trigger a layout even if set to the same bounds since
 // one of the children has changed.
 forceLayout = true;
     }
     
     /**
      * Flushes all cached information about this node and all of its children.
      * This should be called if something may have caused all children to become
      * out of synch with their cached information (for example, if a lot of changes
      * may have happened without calling flushCache after each change)
      *
      * @since 3.1
      */
     public void flushChildren() {
         flushNode();
     }
     
     /**
      * Flushes all cached information about this node and all of its ancestors.
      * This should be called when a single child changes.
      *
      * @since 3.1
      */
     public final void flushCache() {
         flushNode();
         
         if (parent != null) {
             parent.flushCache();
         }
     }
     
     public final int getSizeFlags(boolean width) {
         if (sizeFlagsDirty) {
             widthSizeFlags = doGetSizeFlags(true);
             heightSizeFlags = doGetSizeFlags(false);
             sizeFlagsDirty = false;
         }
         
         return width ? widthSizeFlags : heightSizeFlags;
     }
         
     /**
      * Returns the parent of this tree or null if it is the root.
      */
     public LayoutTreeNode getParent() {
         return parent;
     }

     /**
      * Inserts a new child on the tree. The child will be placed beside
      * the <code>relative</code> child. Returns the new root of the tree.
      */
     public LayoutTree insert(LayoutPart child, boolean left,
             LayoutPartSash sash, LayoutPart relative) {
         LayoutTree relativeChild = find(relative);
         LayoutTreeNode node = new LayoutTreeNode(sash);
         if (relativeChild == null) {
             //Did not find the relative part. Insert beside the root.
 node.setChild(left, child);
             node.setChild(!left, this);
             return node;
         } else {
             LayoutTreeNode oldParent = relativeChild.getParent();
             node.setChild(left, child);
             node.setChild(!left, relativeChild);
             if (oldParent == null) {
                 //It was the root. Return a new root.
 return node;
             }
             oldParent.replaceChild(relativeChild, node);
             return this;
         }
     }

     /**
      * Returns true if this tree can be compressed and expanded.
      * @return true if springy
      */
     public boolean isCompressible() {
         //Added for bug 19524
 return part.isCompressible();
     }

     /**
      * Returns true if this tree has visible parts otherwise returns false.
      */
     public boolean isVisible() {
         return !(part instanceof PartPlaceholder);
     }

     /**
      * Recompute the ratios in this tree.
      */
     public void recomputeRatio() {
     }

     /**
      * Find a child in the tree and remove it and its parent.
      * The other child of its parent is placed on the parent's parent.
      * Returns the new root of the tree.
      */
     public LayoutTree remove(LayoutPart child) {
         LayoutTree tree = find(child);
         if (tree == null) {
             return this;
         }
         LayoutTreeNode oldParent = tree.getParent();
         if (oldParent == null) {
             //It was the root and the only child of this tree
 return null;
         }
         if (oldParent.getParent() == null) {
             return oldParent.remove(tree);
         }

         oldParent.remove(tree);
         return this;
     }

     /**
      * Sets the bounds of this node. If the bounds have changed or any children have
      * changed then the children will be recursively layed out. This implementation
      * filters out redundant calls and delegates to doSetBounds to layout the children.
      * Subclasses should overload doSetBounds to lay out their children.
      *
      * @param bounds new bounds of the tree
      */
     public final void setBounds(Rectangle bounds) {
         if (!bounds.equals(currentBounds) || forceLayout) {
             currentBounds = Geometry.copy(bounds);
             
             doSetBounds(currentBounds);
             forceLayout = false;
         }
     }
     
     /**
      * Resize the parts on this tree to fit in <code>bounds</code>.
      */
     protected void doSetBounds(Rectangle bounds) {
         part.setBounds(bounds);
     }

     /**
      * Set the parent of this tree.
      */
     void setParent(LayoutTreeNode parent) {
         this.parent = parent;
     }

     /**
      * Set the part of this leaf
      */
     void setPart(LayoutPart part) {
         this.part = part;
         flushCache();
     }

     /**
      * Returns a string representation of this object.
      */
     public String toString() {
         return "(" + part.toString() + ")";//$NON-NLS-2$//$NON-NLS-1$
 }

     /**
      * Creates SWT controls owned by the LayoutTree (ie: the sashes). Does not affect the
      * LayoutParts that are being arranged by the LayoutTree.
      *
      * @param parent
      * @since 3.1
      */
     public void createControl(Composite parent) {
     }
         
     /**
      * Writes a description of the layout to the given string buffer.
      * This is used for drag-drop test suites to determine if two layouts are the
      * same. Like a hash code, the description should compare as equal iff the
      * layouts are the same. However, it should be user-readable in order to
      * help debug failed tests. Although these are english readable strings,
      * they should not be translated or equality tests will fail.
      * <p>
      * This is only intended for use by test suites.
      * </p>
      *
      * @param buf
      */
     public void describeLayout(StringBuffer buf) {
         part.describeLayout(buf);
     }

     /**
      * This is a shorthand method that checks if the tree contains the
      * given size flag. For example, hasSizeFlag(false, SWT.MIN) returns
      * true iff the receiver enforces a minimum height, or
      * hasSizeFlag(true, SWT.WRAP) returns true iff the receiver needs to
      * know its height when computing its preferred width.
      *
      * @param vertical
      * @return
      * @since 3.1
      */
     public final boolean hasSizeFlag(boolean width, int flag) {
         return (getSizeFlags(width) & flag) != 0;
     }

 }

